import socket
import pickle
import random
from communication import *
import walker

#MISSING: Locks
#A way to find files
#Handle all peerdisconnected errors
#Complete the exercises
#Write report
#Pass exam
#Get wasted


class PeerManager:
  def __init__(self, name, neighbors, port):
    self._name = name
    self._neighbors = neighbors
    self._plist = [(name, neighbors, socket.gethostbyname(socket.gethostname()), port)]
    self._nlist = []
    self.fman = None
    self._maxplist = 20 + 5 * neighbors
    self._found_files = {}
    #self._nlist_lock = threading.Lock()
    
  def file_found(self, fname, peer):
    found = self._found_files.get(fname)
    if found is not None:
      found(fname, peer)
      del self._found_files[fname]
 
  def init_search(self, fname, ttl, k, found):
    self._found_files[fname] = found
    
    here = self.fman.find(fname)
    if here is not None:
      self.file_found(fname, here)
    
    for i in range(k):
      neighbor = random.choice(self._nlist)
      walk = walker.Walker(ttl, fname, neighbor, self._plist[0], self.plist[0])
      walk.run()
    
  def __getattr__(self, name):
    if name == "nlist":
      return self._nlist
    elif name == "plist":
      return self._plist
    elif name == "name":
      return self._name
    elif name == "neighbors":
      return self._neighbors
    elif name == "filemanager":
      return self.fman
    elif name == "nlist_lock":
      return self._nlist_lock

  def pappend(self, peer):
    if not peer in self._plist and len(self._plist) < self._maxplist:
      self._plist.append(peer)

  def remove(self, peer):
    name, neighbors, ip, port = peer
    if peer in self._plist:
      self._plist.remove(peer)
    self.nremove(peer)
    
  def nremove(self, peer):
    name, neighbors, ip, port = peer
    if peer in self._nlist:
      self._nlist.remove(peer)
    self.fman.remove_nflist(name)

  def nappend(self, thepeer):
    peer, numberofneighbors = thepeer
    name, neighbors, ip, port = peer
    
    if peer in self._nlist:
      return REJECTED
    
    if len(self._nlist) < self.neighbors:
      self._nlist.append(peer)
      return ACCEPTED
    else:
      self._nlist.sort(lambda p1, p2: cmp(p1[1], p2[1]))
      cname, cneighbors, cip, cport = self._nlist[-1]
      if neighbors < cneighbors:
        return REJECTED
      
      cand = None
      candneighbors = 0
      for p in self._nlist:
        if cand is None:
          cand = p
        if candneighbors < p[1]:
          n, cmd, temp = self.comm(p, NNEIGHBORS)
          if cmd != OK:
            continue
          
          if candneighbors < temp:
            candneighbors = temp
            cand = p
      
      if neighbors > self._nlist[0][1] or candneighbors > numberofneighbors+1: #Magic H constant.
        #Throw away cand, return accept
        self.comm(cand, DROPPED, self._plist[0])
        self._nlist.append(peer)
        self.nremove(cand)
        return ACCEPTED
      else:
        return REJECTED
            
    
  def comm(self, peer, cmd, data=None):
    name, neighbors, ip, port = peer
    try:
      return comm(ip, port, self._name, cmd, data)
    except PeerDisconnectedError:
      self.remove(peer)
      return (None, None, None)
    
  ##Method for creating graphviz document. It's magic - DO NOT TOUCH!
  def creategraph(self, peers):
    selectedpeers = []
    neighborhood = []
    tmplist = []
    
    for name, neighbors, ip, port in self._plist:
      for p in peers:
          if name == p:
              selectedpeers.append((name, neighbors, ip, port))
    for peer in selectedpeers:
      tmplist.append(peer)
    for apeer in selectedpeers:
      if apeer[0] != self._plist[0][0]:
        print "Contacting " + str(apeer)
        name, cmd, nlist = self.comm(apeer, NLIST)
        print "Done."
      else:
        name = self._plist[0]
        cmd = OK
        nlist = self._nlist
      if cmd == OK:
        for npeer in nlist:
            if not (apeer, npeer) in neighborhood and not (npeer, apeer) in neighborhood:
                if apeer not in tmplist:
                  tmplist.append(apeer)
                if npeer not in tmplist:
                  tmplist.append(npeer)
                neighborhood.append((apeer, npeer))
      else:
        selectedpeers.remove(apeer)
    ##List created - make .dot document.
    graph = "graph network {\n"
    for name, neighbors, ip, port in tmplist:
        graph += "\"" + name + "(" + str(neighbors) + ")\";\n"
    for apeer, npeer in neighborhood:
        graph += "\"" + apeer[0] + "(" + str(apeer[1]) + ")\" -- \"" + npeer[0] + "(" + str(npeer[1]) + ")\";\n"
    graph += "}\n"
    return graph
  
  ##Hello method which also searches for neighbors
  def hello(self, ip, port):
    self.traverseplist(self.boot(ip, port))
    self.getneighbors()

  ##Bootstrapping for Hello method for peer discovery with bootstrap ip
  def boot(self, ip, port):
    data = []
    try:
      name, cmd, data = comm(ip, port, self._name, HELLO, self._plist[0])
    except PeerDisconnectedError:
      pass
    return data
      
  ##Traverse each peer in plist with "Hello" requests - recursive for the win
  def traverseplist(self, plist):
    random.shuffle(plist)
    for peer in plist:
      if not peer in self._plist:
        self._plist.append(peer)
        if len(self._plist) >= self._maxplist:
          return
        
        name, neighbors, ip, port = peer
        self.traverseplist(self.boot(ip, port))
        
  ##Get neighbors by asking peers wfmanith most to fewest max neighbors.
  def getneighbors(self):
    i = 0
    priolist = self._createprioritylist()
    while i < len(priolist) and len(self._nlist) < self._neighbors:
      peer = priolist[i]
      fromname, cmd, data = self.comm(peer, NEIGHBOR, (self._plist[0], len(self._nlist)))
      
      if cmd == ACCEPTED:
        self._nlist.append(priolist[i])
        self.fman.addnflist(fromname, data)
        self.comm(peer, NFLIST, self.fman.flist)
      i += 1
      
  def neighborsbyname(self, nname):
    for name, neighbors, ip, port in self._nlist:
      if name == nname:
        return (name, neighbors, ip, port)
      
    return None
      
  def isneighbor(self, fromname):
    for name, neighbors, ip, port in self._nlist:
      if fromname == name:
        return 1
    return 0
      
  ##Creates a list with negihborhood priorities
  def _createprioritylist(self):
    priolist = list(self._plist)
    priolist.pop(0)
    priolist.sort(lambda p1, p2: cmp(p1[1], p2[1]))
    return priolist
